package com.example.dictionary.service;

import com.example.memento.model.WordModel;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;

/**
 * 词汇领域管理服务
 * 用于管理词汇领域（如：医学、法律、计算机等）的增删改查
 * 领域数据存储在 data/dict/domains.json 文件中
 * 领域词映射存储在 data/dict/domain-mapping.json 文件中
 */
@Service
public class DomainManagementService {
    
    private static final Logger log = LoggerFactory.getLogger(DomainManagementService.class);
    
    private static final String DOMAIN_FILE_PATH = "data/dict/domains.json";
    private static final String DOMAIN_MAPPING_FILE_PATH = "data/dict/domain-mapping.json";
    
    private final ObjectMapper objectMapper;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    /**
     * 领域列表
     */
    private List<Domain> domains = new ArrayList<>();
    
    /**
     * 领域词映射（内存级别）
     * Key: 当前领域词（如：食物）
     * Value: 实际落地领域词（如：餐饮食品）
     */
    private Map<String, String> domainWordMapping = new HashMap<>();
    
    /**
     * 词典委托，用于查询同义词
     */
    private final DictionaryDelegate dictionaryDelegate;
    
    @Autowired
    public DomainManagementService(DictionaryDelegate dictionaryDelegate) {
        this.dictionaryDelegate = dictionaryDelegate;
        this.objectMapper = new ObjectMapper();
        // 注册 JavaTimeModule 以支持 LocalDateTime 等 Java 8 时间类型的序列化
        this.objectMapper.registerModule(new JavaTimeModule());
        // 禁用将日期写为时间戳，使用 ISO-8601 字符串格式
        this.objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
    
    /**
     * 应用启动时加载领域文件和映射文件
     */
    @PostConstruct
    public void loadDomains() {
        loadDomainList();
        loadDomainMapping();
    }
    
    /**
     * 加载领域列表
     */
    private void loadDomainList() {
        Path domainFile = Paths.get(DOMAIN_FILE_PATH);
        
        // 确保目录存在
        try {
            if (domainFile.getParent() != null) {
                Files.createDirectories(domainFile.getParent());
            }
        } catch (IOException e) {
            log.error("创建领域文件目录失败: {}", domainFile.getParent(), e);
            return;
        }
        
        // 如果文件不存在，创建空列表
        if (!Files.exists(domainFile)) {
            log.info("领域文件不存在，将创建新文件: {}", domainFile);
            saveDomains();
            return;
        }
        
        // 读取并加载领域列表
        lock.writeLock().lock();
        try {
            String json = Files.readString(domainFile);
            if (json == null || json.trim().isEmpty()) {
                log.info("领域文件为空，使用空列表");
                domains = new ArrayList<>();
            } else {
                TypeFactory typeFactory = objectMapper.getTypeFactory();
                CollectionType listType = typeFactory.constructCollectionType(
                    List.class, 
                    Domain.class
                );
                List<Domain> loadedDomains = objectMapper.readValue(json, listType);
                domains = loadedDomains != null ? loadedDomains : new ArrayList<>();
                log.info("成功加载 {} 个领域", domains.size());
            }
        } catch (IOException e) {
            log.error("加载领域文件失败: {}", domainFile, e);
            domains = new ArrayList<>();
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 加载领域词映射
     */
    private void loadDomainMapping() {
        Path mappingFile = Paths.get(DOMAIN_MAPPING_FILE_PATH);
        
        // 确保目录存在
        try {
            if (mappingFile.getParent() != null) {
                Files.createDirectories(mappingFile.getParent());
            }
        } catch (IOException e) {
            log.error("创建映射文件目录失败: {}", mappingFile.getParent(), e);
            return;
        }
        
        // 如果文件不存在，创建空映射
        if (!Files.exists(mappingFile)) {
            log.info("领域映射文件不存在，将创建新文件: {}", mappingFile);
            saveDomainMapping();
            return;
        }
        
        // 读取并加载映射
        lock.writeLock().lock();
        try {
            String json = Files.readString(mappingFile);
            if (json == null || json.trim().isEmpty()) {
                log.info("映射文件为空，使用空映射");
                domainWordMapping = new HashMap<>();
            } else {
                TypeFactory typeFactory = objectMapper.getTypeFactory();
                MapType mapType = typeFactory.constructMapType(
                    Map.class,
                    String.class,
                    String.class
                );
                Map<String, String> loadedMapping = objectMapper.readValue(json, mapType);
                domainWordMapping = loadedMapping != null ? loadedMapping : new HashMap<>();
                log.info("成功加载 {} 个领域词映射", domainWordMapping.size());
            }
        } catch (IOException e) {
            log.error("加载领域映射文件失败: {}", mappingFile, e);
            domainWordMapping = new HashMap<>();
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 保存领域列表到文件
     */
    private void saveDomains() {
        Path domainFile = Paths.get(DOMAIN_FILE_PATH);
        
        lock.readLock().lock();
        try {
            String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(domains);
            Files.writeString(domainFile, json);
            log.debug("保存 {} 个领域到文件: {}", domains.size(), domainFile);
        } catch (IOException e) {
            log.error("保存领域文件失败: {}", domainFile, e);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 保存领域词映射到文件
     */
    private void saveDomainMapping() {
        Path mappingFile = Paths.get(DOMAIN_MAPPING_FILE_PATH);
        
        lock.readLock().lock();
        try {
            String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(domainWordMapping);
            Files.writeString(mappingFile, json);
            log.debug("保存 {} 个领域词映射到文件: {}", domainWordMapping.size(), mappingFile);
        } catch (IOException e) {
            log.error("保存领域映射文件失败: {}", mappingFile, e);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 添加领域
     * 
     * @param domainName 领域名称（如：医学、法律、计算机等）
     * @param description 领域描述（可选）
     * @return 创建的领域对象
     */
    public Domain addDomain(String domainName, String description) {
        if (domainName == null || domainName.trim().isEmpty()) {
            throw new IllegalArgumentException("领域名称不能为空");
        }
        
        String trimmedName = domainName.trim();
        
        lock.writeLock().lock();
        try {
            // 检查领域是否已存在（不区分大小写）
            Optional<Domain> existingDomain = domains.stream()
                    .filter(d -> d.getName().equalsIgnoreCase(trimmedName))
                    .findFirst();
            
            if (existingDomain.isPresent()) {
                log.warn("领域已存在: {}", trimmedName);
                return existingDomain.get();
            }
            
            Domain domain = new Domain();
            domain.setName(trimmedName);
            domain.setDescription(description != null ? description.trim() : null);
            domain.setCreatedAt(LocalDateTime.now());
            domain.setUpdatedAt(LocalDateTime.now());
            
            domains.add(domain);
            saveDomains();
            
            log.info("新增领域: {} (总计: {} 个)", trimmedName, domains.size());
            return domain;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 更新领域信息
     * 
     * @param domainName 领域名称
     * @param newName 新名称（可选）
     * @param description 新描述（可选）
     * @return 更新后的领域对象，如果不存在则返回空
     */
    public Optional<Domain> updateDomain(String domainName, String newName, String description) {
        if (domainName == null || domainName.trim().isEmpty()) {
            throw new IllegalArgumentException("领域名称不能为空");
        }
        
        String trimmedName = domainName.trim();
        
        lock.writeLock().lock();
        try {
            Optional<Domain> domainOpt = domains.stream()
                    .filter(d -> d.getName().equalsIgnoreCase(trimmedName))
                    .findFirst();
            
            if (domainOpt.isEmpty()) {
                log.warn("领域不存在，无法更新: {}", trimmedName);
                return Optional.empty();
            }
            
            Domain domain = domainOpt.get();
            
            // 如果要更新名称，检查新名称是否已存在
            if (newName != null && !newName.trim().isEmpty() && !newName.trim().equalsIgnoreCase(trimmedName)) {
                String trimmedNewName = newName.trim();
                boolean nameExists = domains.stream()
                        .anyMatch(d -> d != domain && d.getName().equalsIgnoreCase(trimmedNewName));
                
                if (nameExists) {
                    throw new IllegalArgumentException("新领域名称已存在: " + trimmedNewName);
                }
                
                domain.setName(trimmedNewName);
            }
            
            if (description != null) {
                domain.setDescription(description.trim());
            }
            
            domain.setUpdatedAt(LocalDateTime.now());
            saveDomains();
            
            log.info("更新领域: {} -> {}", trimmedName, domain.getName());
            return Optional.of(domain);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 删除领域
     * 
     * @param domainName 领域名称
     * @return 是否成功删除
     */
    public boolean removeDomain(String domainName) {
        if (domainName == null || domainName.trim().isEmpty()) {
            return false;
        }
        
        String trimmedName = domainName.trim();
        
        lock.writeLock().lock();
        try {
            boolean removed = domains.removeIf(d -> d.getName().equalsIgnoreCase(trimmedName));
            
            if (removed) {
                saveDomains();
                log.info("删除领域: {} (剩余: {} 个)", trimmedName, domains.size());
            } else {
                log.warn("领域不存在，无法删除: {}", trimmedName);
            }
            
            return removed;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 获取领域
     * 
     * @param domainName 领域名称
     * @return 领域对象（如果存在）
     */
    public Optional<Domain> getDomain(String domainName) {
        if (domainName == null || domainName.trim().isEmpty()) {
            return Optional.empty();
        }
        
        String trimmedName = domainName.trim();
        
        lock.readLock().lock();
        try {
            return domains.stream()
                    .filter(d -> d.getName().equalsIgnoreCase(trimmedName))
                    .findFirst();
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 检查领域是否存在
     * 
     * @param domainName 领域名称
     * @return 是否存在
     */
    public boolean existsDomain(String domainName) {
        return getDomain(domainName).isPresent();
    }
    
    /**
     * 获取所有领域
     * 
     * @return 所有领域的副本
     */
    public List<Domain> getAllDomains() {
        lock.readLock().lock();
        try {
            return new ArrayList<>(domains);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 获取所有领域名称
     * 
     * @return 领域名称列表
     */
    public List<String> getAllDomainNames() {
        lock.readLock().lock();
        try {
            return domains.stream()
                    .map(Domain::getName)
                    .collect(Collectors.toList());
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 搜索领域（按名称模糊匹配）
     * 
     * @param keyword 关键词
     * @return 匹配的领域列表
     */
    public List<Domain> searchDomains(String keyword) {
        if (keyword == null || keyword.trim().isEmpty()) {
            return getAllDomains();
        }
        
        String lowerKeyword = keyword.toLowerCase().trim();
        
        lock.readLock().lock();
        try {
            return domains.stream()
                    .filter(d -> d.getName().toLowerCase().contains(lowerKeyword) ||
                               (d.getDescription() != null && 
                                d.getDescription().toLowerCase().contains(lowerKeyword)))
                    .collect(Collectors.toList());
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 获取领域数量
     * 
     * @return 领域数量
     */
    public int getDomainCount() {
        lock.readLock().lock();
        try {
            return domains.size();
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 获取真实领域词
     * 逻辑：
     * 1. 如果映射存在，返回映射的真实领域词
     * 2. 如果映射不存在，但实际落地领域词存在（在domains列表中），返回实际落地领域词
     * 3. 如果都不存在，通过当前词查询同义词：
     *    - 如果出现四字同义词，则使用并添加到映射
     *    - 如果没有四字同义词，使用当前词作为领域词并进行存储
     * 4. 如果同义词在现有领域词列表中，则添加到领域词映射
     * 
     * @param domainWord 当前领域词（如：食物）
     * @return 真实领域词（如：餐饮食品）
     */
    public String getActualDomainWord(String domainWord) {
        if (domainWord == null || domainWord.trim().isEmpty()) {
            return domainWord;
        }
        
        String trimmedWord = domainWord.trim();
        
        lock.writeLock().lock();
        try {
            // 1. 检查映射是否存在
            String mappedWord = domainWordMapping.get(trimmedWord);
            if (mappedWord != null && !mappedWord.trim().isEmpty()) {
                log.debug("从映射获取真实领域词: {} -> {}", trimmedWord, mappedWord);
                return mappedWord;
            }
            
            // 2. 检查实际落地领域词是否存在（在domains列表中）
            boolean existsAsDomain = domains.stream()
                    .anyMatch(d -> d.getName().equalsIgnoreCase(trimmedWord));
            
            if (existsAsDomain) {
                log.debug("领域词已存在于领域列表: {}", trimmedWord);
                return trimmedWord;
            }
            
            // 3. 查询同义词
            String actualWord = findActualWordFromSynonyms(trimmedWord);
            
            // 4. 如果同义词在现有领域词列表中，添加到映射
            if (!actualWord.equals(trimmedWord)) {
                boolean actualWordExists = domains.stream()
                        .anyMatch(d -> d.getName().equalsIgnoreCase(actualWord));
                if (actualWordExists) {
                    domainWordMapping.put(trimmedWord, actualWord);
                    saveDomainMapping();
                    log.info("同义词在领域列表中，添加映射: {} -> {}", trimmedWord, actualWord);
                }
            }
            
            return actualWord;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 从同义词中查找实际领域词
     * 优先使用四字同义词，如果没有则使用当前词
     * 
     * @param domainWord 当前领域词
     * @return 实际领域词
     */
    private String findActualWordFromSynonyms(String domainWord) {
        try {
            // 查询词汇对象
            Optional<WordModel> wordModelOpt = dictionaryDelegate.findWord(domainWord);
            
            if (wordModelOpt.isEmpty()) {
                log.debug("未找到词汇对象，使用当前词: {}", domainWord);
                // 如果词汇不存在，使用当前词作为领域词并存储
                ensureDomainExists(domainWord);
                return domainWord;
            }
            
            WordModel wordModel = wordModelOpt.get();
            
            // 收集所有可能的同义词（包括主词名和完整词名）
            List<String> allSynonyms = new ArrayList<>();
            if (wordModel.getPrimaryWord() != null) {
                allSynonyms.add(wordModel.getPrimaryWord());
            }
            if (wordModel.getFullWordNames() != null) {
                allSynonyms.addAll(wordModel.getFullWordNames());
            }
            
            // 优先查找四字同义词
            Optional<String> fourCharSynonym = allSynonyms.stream()
                    .filter(syn -> syn != null && syn.length() == 4)
                    .filter(syn -> !syn.equals(domainWord))
                    .findFirst();
            
            if (fourCharSynonym.isPresent()) {
                String fourCharWord = fourCharSynonym.get();
                log.info("找到四字同义词: {} -> {}", domainWord, fourCharWord);
                
                // 确保四字同义词存在于领域列表中
                ensureDomainExists(fourCharWord);
                
                // 添加映射
                domainWordMapping.put(domainWord, fourCharWord);
                saveDomainMapping();
                
                return fourCharWord;
            }
            
            // 没有四字同义词，检查是否有其他同义词在领域列表中
            for (String synonym : allSynonyms) {
                if (synonym != null && !synonym.equals(domainWord)) {
                    boolean exists = domains.stream()
                            .anyMatch(d -> d.getName().equalsIgnoreCase(synonym));
                    if (exists) {
                        log.info("找到在领域列表中的同义词: {} -> {}", domainWord, synonym);
                        domainWordMapping.put(domainWord, synonym);
                        saveDomainMapping();
                        return synonym;
                    }
                }
            }
            
            // 没有找到合适的同义词，使用当前词
            log.debug("未找到合适的同义词，使用当前词: {}", domainWord);
            ensureDomainExists(domainWord);
            return domainWord;
            
        } catch (Exception e) {
            log.error("查询同义词失败: {}", domainWord, e);
            // 发生错误时，确保领域存在并使用当前词
            ensureDomainExists(domainWord);
            return domainWord;
        }
    }
    
    /**
     * 确保领域存在于领域列表中
     * 
     * @param domainName 领域名称
     */
    private void ensureDomainExists(String domainName) {
        boolean exists = domains.stream()
                .anyMatch(d -> d.getName().equalsIgnoreCase(domainName));
        
        if (!exists) {
            Domain domain = new Domain();
            domain.setName(domainName);
            domain.setCreatedAt(LocalDateTime.now());
            domain.setUpdatedAt(LocalDateTime.now());
            domains.add(domain);
            saveDomains();
            log.info("自动创建领域: {}", domainName);
        }
    }
    
    /**
     * 添加领域词映射
     * 
     * @param domainWord 当前领域词
     * @param actualDomainWord 实际落地领域词
     */
    public void addDomainMapping(String domainWord, String actualDomainWord) {
        if (domainWord == null || domainWord.trim().isEmpty()) {
            throw new IllegalArgumentException("领域词不能为空");
        }
        if (actualDomainWord == null || actualDomainWord.trim().isEmpty()) {
            throw new IllegalArgumentException("实际领域词不能为空");
        }
        
        String trimmedDomainWord = domainWord.trim();
        String trimmedActualWord = actualDomainWord.trim();
        
        lock.writeLock().lock();
        try {
            String existingMapping = domainWordMapping.get(trimmedDomainWord);
            if (existingMapping != null && existingMapping.equals(trimmedActualWord)) {
                log.debug("映射已存在: {} -> {}", trimmedDomainWord, trimmedActualWord);
                return;
            }
            
            domainWordMapping.put(trimmedDomainWord, trimmedActualWord);
            saveDomainMapping();
            log.info("添加领域词映射: {} -> {}", trimmedDomainWord, trimmedActualWord);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 删除领域词映射
     * 
     * @param domainWord 领域词
     */
    public boolean removeDomainMapping(String domainWord) {
        if (domainWord == null || domainWord.trim().isEmpty()) {
            return false;
        }
        
        String trimmedWord = domainWord.trim();
        
        lock.writeLock().lock();
        try {
            String removed = domainWordMapping.remove(trimmedWord);
            if (removed != null) {
                saveDomainMapping();
                log.info("删除领域词映射: {} -> {}", trimmedWord, removed);
                return true;
            }
            return false;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    /**
     * 获取领域词映射
     * 
     * @param domainWord 领域词
     * @return 映射的实际领域词（如果存在）
     */
    public Optional<String> getDomainMapping(String domainWord) {
        if (domainWord == null || domainWord.trim().isEmpty()) {
            return Optional.empty();
        }
        
        lock.readLock().lock();
        try {
            return Optional.ofNullable(domainWordMapping.get(domainWord.trim()));
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 获取所有领域词映射
     * 
     * @return 所有映射的副本
     */
    public Map<String, String> getAllDomainMappings() {
        lock.readLock().lock();
        try {
            return new HashMap<>(domainWordMapping);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    /**
     * 领域实体类
     */
    public static class Domain {
        /**
         * 领域名称（如：医学、法律、计算机等）
         */
        private String name;
        
        /**
         * 领域描述
         */
        private String description;
        
        /**
         * 创建时间
         */
        private LocalDateTime createdAt;
        
        /**
         * 更新时间
         */
        private LocalDateTime updatedAt;
        
        public String getName() {
            return name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public String getDescription() {
            return description;
        }
        
        public void setDescription(String description) {
            this.description = description;
        }
        
        public LocalDateTime getCreatedAt() {
            return createdAt;
        }
        
        public void setCreatedAt(LocalDateTime createdAt) {
            this.createdAt = createdAt;
        }
        
        public LocalDateTime getUpdatedAt() {
            return updatedAt;
        }
        
        public void setUpdatedAt(LocalDateTime updatedAt) {
            this.updatedAt = updatedAt;
        }
        
        @Override
        public String toString() {
            return "Domain{" +
                    "name='" + name + '\'' +
                    ", description='" + description + '\'' +
                    ", createdAt=" + createdAt +
                    ", updatedAt=" + updatedAt +
                    '}';
        }
    }
}

